﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Hiro.ActivationPoints.Interfaces;
using Hiro.Interfaces;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Hiro.ActivationPoints
{
    /// <summary>
    /// Represents an activation point that is used when instantiating a particular service type.
    /// </summary>
    public class TypeActivationPoint : ITypeActivationPoint
    {
        /// <summary>
        /// The list of possible constructor calls that will be used to instantiate the current type.
        /// </summary>
        private readonly List<IConstructorActivationPoint> _constructorActivationPoints = new List<IConstructorActivationPoint>();

        /// <summary>
        /// The list of property activation points that will be used to implement property injection.
        /// </summary>
        private readonly List<IPropertyActivationPoint> _propertyActivationPoints = new List<IPropertyActivationPoint>();

        /// <summary>
        /// The lock synchronization object.
        /// </summary>
        private readonly object _lock = new object();

        /// <summary>
        /// The emitter flag.
        /// </summary>
        private bool _emitInProgress;

        /// <summary>
        /// Initializes a new instance of the TypeActivationPoint class.
        /// </summary>
        /// <param name="targetType">The concrete type that can implement a given service.</param>
        public TypeActivationPoint(Type targetType)
        {
            TargetType = targetType;
        }        
      
        /// <summary>
        /// Gets a value indicating the target type that will be instantiated if
        /// this activation point is activated.
        /// </summary>
        /// <value>The target type itself.</value>
        public Type TargetType
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the value indicating the list of <see cref="IConstructorActivationPoint"/> objects
        /// that will be used to instantiate the service type.
        /// </summary>
        /// <value>A list containing the list of available constructor activation points.</value>
        public IList<IConstructorActivationPoint> ConstructorActivationPoints
        {
            get { return _constructorActivationPoints; }
        }

        /// <summary>
        /// Gets the value indicating the list of <see cref="IPropertyActivationPoint"/> objects
        /// that will be used to perform property injection operations on the newly-instantiated service type.
        /// </summary>
        public IList<IPropertyActivationPoint> PropertyActivationPoints
        {
            get { return _propertyActivationPoints; }
        }

        /// <summary>
        /// Determines whether or not the current <see cref="IDependencyMap"/> has the necessary
        /// dependencies for the current activation point to emit the necessary IL to instantiate the current type.
        /// </summary>
        /// <param name="dependencyContainer">The target dependency container.</param>
        /// <returns><c>true</c> if all required dependencies can be instantiated; otherwise, it will return <c>false</c>.</returns>
        public bool IsValidWith(IDependencyContainer dependencyContainer)
        {
            var result = false;
            foreach (var point in _constructorActivationPoints)
            {
                result |= point.IsValidWith(dependencyContainer);
                if (!result)
                    break;
            }

            return result;
        }

        /// <summary>
        /// Emits the necessary <paramref name="IL"/> instructions to instantiate the current type.
        /// </summary>
        /// <param name="targetMethod">The method that will instantiate the current type.</param>
        /// <param name="IL">The <see cref="CilWorker"/> that points to the method body of the method that will instantiate the current type.</param>
        /// <param name="hostModule">The module that contains the container type.</param>
        /// <param name="dependencyMap">The dependency container that describes the list of dependencies in the current application.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "IL", Justification = "The parameter is named 'IL' since the class is working with an IL generator.")]
        public void Emit(MethodReference targetMethod, CilWorker IL, ModuleDefinition hostModule, IDependencyMap dependencyMap)
        {          
            // Determine which constructors can be resolved
            var validConstructors = (from c in _constructorActivationPoints
                                     where c.IsValidWith(dependencyMap)
                                     select c).ToList();

            if (validConstructors.Count == 0)
                throw new ConstructorNotFoundException();

            // Use the constructor with the most number of resolvable parameters
            IActivationPoint bestMatch = null;
            var bestParameterCount = 0;
            foreach (var point in validConstructors)
            {
                var constructor = point.TargetConstructor;
                var parameters = constructor.GetParameters();
                var parameterCount = parameters.Length;

                if (parameterCount < bestParameterCount)
                    continue;

                bestMatch = point;
                bestParameterCount = parameterCount;
            }

            if (bestMatch == null)
                throw new ConstructorNotFoundException();

            bestMatch.Emit(targetMethod, IL, hostModule, dependencyMap);

            if (_emitInProgress)
                return;

            lock (_lock)
            {
                _emitInProgress = true;
                var validProperties = from p in _propertyActivationPoints
                                      where p.IsValidWith(dependencyMap)
                                      select p;

                foreach (var point in validProperties)
                {
                    point.Emit(targetMethod, IL, hostModule, dependencyMap);
                }

                _emitInProgress = false;
            }
            return;
        }
    }
}
